import { RepoLink, Link } from 'libframe-docs/components'

> HTML Streaming support is a beta feature; breaking changes may be introduced in minor version updates.

## Basics

```js
// renderer/_deault.page.server.js

export { render }

import { escapeInject } from 'vite-plugin-ssr'
import { renderToStream } from 'some-ui-framework' // React, Vue, ...

async function render(pageContext) {
  const { Page } = pageContext

  const stream = renderToStream(Page)

  return escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-view">${stream}</div>
      </body>
    </html>`
}
```

```js
// server.js

app.get("*", async (req, res, next) => {
  const pageContextInit = { url: req.url }
  const pageContext = await renderPage(pageContextInit)
  const { httpResponse } = pageContext
  if (!httpResponse) return next()

  // If `renderToStream()` returns a Node.js Stream:
  const stream = await httpResponse.getNodeStream()
  // If `renderToStream()` returns a Web Stream:
  const stream = await httpResponse.getWebStream()

  stream.pipe(res)
})
```

## Examples & docs

Examples:
 - With React's `renderToNodeStream()`: <RepoLink path='/examples/react-full/' />
 - With Vue's `renderToNodeStream()`: <RepoLink path='/examples/vue-full/' />
 - With Cloudflare Workers and Vue's `pipeToWebWritable()`: <RepoLink path='/examples/cloudflare-workers-vue-html-streaming/' />

Stream Docs & API:
 - [Node.js Streams](https://nodejs.org/api/stream.html)
 - [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API)


## Stream to string

We can convert the stream to a string:

```js
  // If we provide a stream into our `render()` hook's `escapeInject`, we can
  // still get a string.

  /* This won't work: (a stream cannot be consumed synchronously)
  const { body } = httpResponse
  res.send(body)
  */

  // But we can do:
  const body = await httpResponse.getBody()
  assert(typeof body === 'string')
  res.send(body)
```

## Stream pipes

We can also use Stream pipes.

```js
// renderer/_deault.page.server.js

export { render }

import { escapeInject, pipeNodeStream, pipeWebStream } from 'vite-plugin-ssr'
import { pipeToWritable } from 'some-ui-framework' // React, Vue, ...

async function render(pageContext) {
  const { Page } = pageContext

  // Node.js Stream
  const streamPipe = pipeNodeStream(writable => {
    // `writable` is a Node.js writable
    pipeToWritable(Page, writable)
  })
  // Web Stream
  const streamPipe = pipeWebStream(writable => {
    // `writable` is a Web writable
    pipeToWritable(Page, writable)
  })

  return escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-view">${streamPipe}</div>
      </body>
    </html>`
}
```
```js
// server.js

app.get("*", async (req, res, next) => {
  const pageContextInit = { url: req.url }
  const pageContext = await renderPage(pageContextInit)
  const { httpResponse } = pageContext
  if (!httpResponse) return next()

  // Node.js Stream
  httpResponse.pipeToNodeWritable(res)
  // Web Stream
  httpResponse.pipeToWebWritable(res)
})
```

## Initial data after streaming

Some data fetching tools, such as <Link href="/relay" text="Relay">Relay</Link>, provide the initial data only after the stream as ended.

In such situations, we can return a `pageContext` promise in our `render()` hook:

```js
// renderer/_deault.page.server.js

export { render }
export { passToClient }

import { escapeInject } from 'vite-plugin-ssr'
import { renderToStream } from 'some-ui-framework' // React, Vue, ...

const passToClient = ['initialData']

async function render(pageContext) {
  const { Page } = pageContext

  const stream = renderToStream(Page)

  const documentHtml = escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-view">${stream}</div>
      </body>
    </html>`

  const pageContextPromise = (async () => {
     return {
       // Some `initialData` provided after the stream has ended
       initialData,
     }
  })()

  return {
    documentHtml,
    pageContext: pageContextPromise
  }
}
```
